{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# akshare股票数据，csv指标文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [],
   "source": [
    "import backtrader as bt  \n",
    "import pandas as pd\n",
    "from datetime import datetime"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 导入股票\n",
    "data_stock"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "            date      open      high       low     close       volume\n",
      "0     1990-12-19    96.050    99.980    95.790    99.980       126000\n",
      "1     1990-12-20   104.300   104.390    99.980   104.390        19700\n",
      "2     1990-12-21   109.070   109.130   103.730   109.130         2800\n",
      "3     1990-12-24   113.570   114.550   109.130   114.550         3200\n",
      "4     1990-12-25   120.090   120.250   114.550   120.250         1500\n",
      "...          ...       ...       ...       ...       ...          ...\n",
      "8171  2024-06-06  3069.439  3077.673  3040.830  3048.794  36986710400\n",
      "8172  2024-06-07  3053.915  3065.025  3031.044  3051.279  31634248400\n",
      "8173  2024-06-11  3042.134  3043.200  3013.861  3028.045  31048194200\n",
      "8174  2024-06-12  3025.296  3042.042  3021.309  3037.468  27945210600\n",
      "8175  2024-06-13  3038.086  3040.402  3022.822  3028.919  29786246300\n",
      "\n",
      "[8176 rows x 6 columns]\n"
     ]
    }
   ],
   "source": [
    "import akshare as ak\n",
    "# 获取上证指数的历史行情数据  \n",
    "data_stock = ak.stock_zh_index_daily(symbol=\"sh000001\")  \n",
    "print(data_stock)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 指标数据文件\n",
    "data_signals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 读取csv\n",
    "data_signals = pd.read_csv('data/bwwmacd1.csv') # 股票指标数据(大智慧导出)\n",
    "# 改变列名称\n",
    "data_signals.columns = ['date', 'M5', 'M10', 'M20']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 日期列转换时间类型\n",
    "data_stock['date'] = pd.to_datetime(data_stock['date'])\n",
    "data_signals['date'] = pd.to_datetime(data_signals['date'])\n",
    "\n",
    "# 时间索引\n",
    "data_stock.index = pd.to_datetime(data_stock['date'])\n",
    "data_signals.index = pd.to_datetime(data_signals['date'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                 date      open      high       low     close       volume\n",
      "date                                                                      \n",
      "1990-12-19 1990-12-19    96.050    99.980    95.790    99.980       126000\n",
      "1990-12-20 1990-12-20   104.300   104.390    99.980   104.390        19700\n",
      "1990-12-21 1990-12-21   109.070   109.130   103.730   109.130         2800\n",
      "1990-12-24 1990-12-24   113.570   114.550   109.130   114.550         3200\n",
      "1990-12-25 1990-12-25   120.090   120.250   114.550   120.250         1500\n",
      "...               ...       ...       ...       ...       ...          ...\n",
      "2024-06-06 2024-06-06  3069.439  3077.673  3040.830  3048.794  36986710400\n",
      "2024-06-07 2024-06-07  3053.915  3065.025  3031.044  3051.279  31634248400\n",
      "2024-06-11 2024-06-11  3042.134  3043.200  3013.861  3028.045  31048194200\n",
      "2024-06-12 2024-06-12  3025.296  3042.042  3021.309  3037.468  27945210600\n",
      "2024-06-13 2024-06-13  3038.086  3040.402  3022.822  3028.919  29786246300\n",
      "\n",
      "[8176 rows x 6 columns]\n",
      "                 date      M5     M10    M20\n",
      "date                                        \n",
      "1990-12-19 1990-12-19     NaN     NaN   -2.5\n",
      "1990-12-20 1990-12-20     NaN     NaN   -2.5\n",
      "1990-12-21 1990-12-21     NaN     NaN   -3.0\n",
      "1990-12-24 1990-12-24     NaN     NaN   -3.5\n",
      "1990-12-25 1990-12-25     NaN     NaN   -2.5\n",
      "...               ...     ...     ...    ...\n",
      "2024-06-03 2024-06-03  -664.6   70.40 -844.0\n",
      "2024-06-04 2024-06-04  -825.3  -37.70 -843.5\n",
      "2024-06-05 2024-06-05  -971.0 -146.75 -846.0\n",
      "2024-06-06 2024-06-06 -1076.7 -262.00 -846.0\n",
      "2024-06-07 2024-06-07 -1141.6 -377.75 -848.0\n",
      "\n",
      "[8173 rows x 4 columns]\n"
     ]
    }
   ],
   "source": [
    "print(data_stock)\n",
    "print(data_signals)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义开始和结束日期  \n",
    "start_date = '2000-01-01'  # 例如：2023年1月1日  \n",
    "end_date = '2024-06-07'   # 例如：2023年6月30日  \n",
    "  \n",
    "# 将字符串转换为datetime对象（如果它们不是的话）  \n",
    "start_date = datetime.strptime(start_date, '%Y-%m-%d')  \n",
    "end_date = datetime.strptime(end_date, '%Y-%m-%d') \n",
    "\n",
    "mask = (data_stock.date >= start_date) & (data_stock.date <= end_date)  \n",
    "data_stock = data_stock.loc[mask] \n",
    "data_signals=data_signals.loc[mask] "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                 date      open      high       low     close       volume\n",
      "date                                                                      \n",
      "2000-01-04 2000-01-04  1368.693  1407.518  1361.214  1406.371    903402300\n",
      "2000-01-05 2000-01-05  1407.829  1433.780  1398.323  1409.682   1057998400\n",
      "2000-01-06 2000-01-06  1406.036  1463.955  1400.253  1463.942   1348051500\n",
      "2000-01-07 2000-01-07  1477.154  1522.825  1477.154  1516.604   3451569900\n",
      "2000-01-10 2000-01-10  1531.712  1546.723  1506.404  1545.112   3125353900\n",
      "...               ...       ...       ...       ...       ...          ...\n",
      "2024-06-03 2024-06-03  3085.981  3097.204  3061.277  3078.489  35756336200\n",
      "2024-06-04 2024-06-04  3071.320  3095.322  3063.588  3091.199  30928759800\n",
      "2024-06-05 2024-06-05  3086.051  3092.346  3064.736  3065.403  29054234400\n",
      "2024-06-06 2024-06-06  3069.439  3077.673  3040.830  3048.794  36986710400\n",
      "2024-06-07 2024-06-07  3053.915  3065.025  3031.044  3051.279  31634248400\n",
      "\n",
      "[5919 rows x 6 columns]\n",
      "                 date      M5     M10    M20\n",
      "date                                        \n",
      "2000-01-04 2000-01-04  -202.5  -72.15 -193.0\n",
      "2000-01-05 2000-01-05  -201.7  -79.55 -194.5\n",
      "2000-01-06 2000-01-06  -166.1  -72.95 -197.5\n",
      "2000-01-07 2000-01-07  -110.1  -59.20 -198.5\n",
      "2000-01-10 2000-01-10   -45.3  -43.20 -197.0\n",
      "...               ...     ...     ...    ...\n",
      "2024-06-03 2024-06-03  -664.6   70.40 -844.0\n",
      "2024-06-04 2024-06-04  -825.3  -37.70 -843.5\n",
      "2024-06-05 2024-06-05  -971.0 -146.75 -846.0\n",
      "2024-06-06 2024-06-06 -1076.7 -262.00 -846.0\n",
      "2024-06-07 2024-06-07 -1141.6 -377.75 -848.0\n",
      "\n",
      "[5919 rows x 4 columns]\n"
     ]
    }
   ],
   "source": [
    "print(data_stock)\n",
    "print(data_signals)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 自定义数据源类\n",
    "这两个类都继承自 bt.feeds.PandasData，意味着它们能够处理Pandas DataFrame格式的数据。\n",
    "\n",
    "在策略中，您可以通过 self.datas[0] 访问股票数据，通过 self.datas[1] 访问信号数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 自定义数据源，这里仅展示框架，具体实现需要根据你的CSV文件格式进行调整  \n",
    "class MyStockData(bt.feeds.PandasData):  # MyStockData 类定义了如何从Pandas DataFrame中提取股票数据。\n",
    "    # params 元组指定了日期、开盘价、最高价、最低价、收盘价和交易量（如果有的话）在DataFrame中的列索引。\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引 \n",
    "        ('high', 1),      # 最高价在DataFrame中的列索引 \n",
    "        ('low', 2),       # 最低价在DataFrame中的列索引  \n",
    "        ('open', 3),      # 开盘价在DataFrame中的列索引  \n",
    "        ('close', 4),     # 收盘价在DataFrame中的列索引  \n",
    "        ('volume', 5),    # 交易量在DataFrame中的列索引（如果有的话）  \n",
    "    )  \n",
    "  \n",
    "class MySignalData(bt.feeds.PandasData):  # MySignalData 类用于处理信号数据。\n",
    "    # 指定了日期和信号在DataFrame中的列索引。\n",
    "    lines = ('M5','M10','M20', )\n",
    "    params = (  \n",
    "        ('datetime', 0),  # 日期在DataFrame中的列索引  \n",
    "        ('high', None),      # 最高价在DataFrame中的列索引 \n",
    "        ('low', None),       # 最低价在DataFrame中的列索引  \n",
    "        ('open', None),      # 开盘价在DataFrame中的列索引  \n",
    "        ('close', None),     # 收盘价在DataFrame中的列索引  \n",
    "        ('volume', None),    # 交易量在DataFrame中的列索引（如果有的话）  \n",
    "        ('M5', 1), # 假设'M5'是某种信号，这里重命名以避免混淆  \n",
    "        ('M10', 2), # 同上  \n",
    "        ('M20', 3), # 同上  \n",
    "    )  \n",
    "    \n",
    "        \n",
    "  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 买卖策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyStrategy(bt.Strategy):\n",
    "    params = (\n",
    "       \n",
    "    )\n",
    "\n",
    "    def __init__(self):\n",
    "        self.order = None\n",
    "\n",
    "        self.datasignalsM5 = self.datas[1].lines.M5  \n",
    "        self.datasignalsM10 = self.datas[1].lines.M10\n",
    "        self.datasignalsM20 = self.datas[1].lines.M20\n",
    "        self.crossover = bt.ind.CrossOver(self.datasignalsM5, self.datasignalsM10)\n",
    "        pass\n",
    "\n",
    "    def downcast(self, amount, lot): #可买入资金\n",
    "\t    #使用整数除法 amount // lot 来找到 amount 可以被 lot 整除的最大次数。\n",
    "        return abs(amount // lot * lot) \n",
    "        \"\"\" \t   \n",
    "\t\tself 的出现意味着这个函数实际上是类的一个方法，而不是一个独立的函数。\n",
    "\t\tclass MyClass:  \n",
    " \t\t   def downcast(self, amount, lot):  \n",
    "  \t\t      return abs(amount // lot * lot)  \n",
    "\n",
    "\t\t# 使用方法  \n",
    "\t\tmy_instance = MyClass()  \n",
    "\t\tresult = my_instance.downcast(123, 10)  \n",
    "\t\tprint(result)  # 输出 120\n",
    "\t    \"\"\"\n",
    "    # 可以不要，但如果你数据未对齐，需要在这里检验\n",
    "    # def prenext(self):\n",
    "    #     print('prenext 执行 ', self.datetime.date(), self.getdatabyname('300015')._name\n",
    "    #           , self.getdatabyname('300015').close[0])\n",
    "    #     pass\n",
    "\n",
    "    def next(self):\n",
    "        # 检查是否有指令执行，如果有则不执行这bar\n",
    "        if self.order:\n",
    "            return\n",
    "        # 回测如果是最后一天，则不进行买卖\n",
    "        if pd.Timestamp(self.data.datetime.date(0)) == end_date:\n",
    "            return\n",
    "        if not self.position:  # 没有持仓\n",
    "            # 执行买入条件判断：收盘价格上涨突破20日均线；\n",
    "            # 不要在股票剔除日前一天进行买入\n",
    "            if self.crossover > 0:  # 如果信号为1，买入  \n",
    "                # buy_price = self.data.close[0]\n",
    "                order_value = self.broker.getvalue() * 0.98\n",
    "                order_amount = self.downcast(order_value / self.data.close[0], 100)\n",
    "                self.order = self.buy(self.datas[0], order_amount, name=self.datas[0]._name)\n",
    "\n",
    "        else:\n",
    "            # 执行卖出条件判断\n",
    "            # down = self.data0.close[0] < buy_price\n",
    "            if self.crossover < 0:\n",
    "                # 执行卖出\n",
    "                self.order = self.order_target_percent(self.datas[0], 0, name=self.datas[0]._name)\n",
    "                self.log(f'卖{self.datas[0]._name},price:{self.data.close[0]:.2f},pct: 0')\n",
    "        pass\n",
    "\n",
    "    def notify_order(self, order): # 用于通知和处理订单信息。\n",
    "        # 检查order.status是否属于两个可能的状态之一：order.Submitted 和 order.Accepted。\n",
    "        if order.status in [order.Submitted, order.Accepted]:\n",
    "            # Buy/Sell order submitted/accepted to/by broker - Nothing to do\n",
    "            return\n",
    "\n",
    "        # Check if an order has been completed\n",
    "        # Attention: broker could reject order if not enough cash\n",
    "        if order.status in [order.Completed, order.Canceled, order.Margin]:\n",
    "            if order.isbuy():\n",
    "                self.log(\n",
    "                    f\"买入{order.info['name']}, 成交量{order.executed.size}，成交价{order.executed.price:.2f} 订单状态：{order.status}\")\n",
    "                self.log('买入后当前资产：%.2f 元' % self.broker.getvalue())\n",
    "            elif order.issell():\n",
    "                self.log(\n",
    "                    f\"卖出{order.info['name']}, 成交量{order.executed.size}，成交价{order.executed.price:.2f} 订单状态：{order.status}\")\n",
    "                self.log('卖出后当前资产：%.2f 元' % self.broker.getvalue())\n",
    "            self.bar_executed = len(self)\n",
    "\n",
    "        # Write down: no pending order\n",
    "        self.order = None\n",
    "\n",
    "    def log(self, txt, dt=None): #日志-日期和信息\n",
    "        \"\"\"\n",
    "        输出日期\n",
    "        :param txt:\n",
    "        :param dt:\n",
    "        :return:\n",
    "        \"\"\"\n",
    "        dt = dt or self.datetime.date(0)  # 现在的日期\n",
    "        #'%s , %s' 是一个字符串模板，其中%s是一个占位符，用于表示一个字符串。\n",
    "        # 在这个模板中，有两个%s，这意味着我们需要为它们提供两个字符串值。\n",
    "        # (dt.isoformat(), txt) 是一个元组，它包含了两个值：dt.isoformat() 和 txt。\n",
    "        # isoformat方法默认会生成一个包含T的ISO 8601格式的字符串（如2023-10-23T14:30:00），\n",
    "        # 所以在输出的日期和时间之间会有一个T\n",
    "        print('%s , %s' % (dt.isoformat(), txt))\n",
    "\n",
    "    pass\n",
    "\n",
    "    def notify_trade(self, trade): #打印交易信息\n",
    "        '''可选，打印交易信息'''\n",
    "        pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2000-05-26 , 买入, 成交量500.0，成交价1885.75 订单状态：4\n",
      "2000-05-26 , 买入后当前资产：995046.75 元\n"
     ]
    },
    {
     "ename": "UnboundLocalError",
     "evalue": "local variable 'buy_price' referenced before assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mUnboundLocalError\u001b[0m                         Traceback (most recent call last)",
      "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_12716\\2627761102.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     37\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     38\u001b[0m \u001b[1;31m# 运行回测\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 39\u001b[1;33m \u001b[0mresults\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mcerebro\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32mc:\\ProgramData\\Anaconda3\\lib\\site-packages\\backtrader\\cerebro.py\u001b[0m in \u001b[0;36mrun\u001b[1;34m(self, **kwargs)\u001b[0m\n\u001b[0;32m   1130\u001b[0m             \u001b[1;31m# let's skip process \"spawning\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1131\u001b[0m             \u001b[1;32mfor\u001b[0m \u001b[0miterstrat\u001b[0m \u001b[1;32min\u001b[0m \u001b[0miterstrats\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1132\u001b[1;33m                 \u001b[0mrunstrat\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrunstrategies\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0miterstrat\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1133\u001b[0m                 \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrunstrats\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrunstrat\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1134\u001b[0m                 \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_dooptimize\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mc:\\ProgramData\\Anaconda3\\lib\\site-packages\\backtrader\\cerebro.py\u001b[0m in \u001b[0;36mrunstrategies\u001b[1;34m(self, iterstrat, predata)\u001b[0m\n\u001b[0;32m   1296\u001b[0m                     \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_runonce_old\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrunstrats\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1297\u001b[0m                 \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1298\u001b[1;33m                     \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_runonce\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mrunstrats\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1299\u001b[0m             \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1300\u001b[0m                 \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0moldsync\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mc:\\ProgramData\\Anaconda3\\lib\\site-packages\\backtrader\\cerebro.py\u001b[0m in \u001b[0;36m_runonce\u001b[1;34m(self, runstrats)\u001b[0m\n\u001b[0;32m   1698\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1699\u001b[0m             \u001b[1;32mfor\u001b[0m \u001b[0mstrat\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrunstrats\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1700\u001b[1;33m                 \u001b[0mstrat\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_oncepost\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdt0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1701\u001b[0m                 \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_event_stop\u001b[0m\u001b[1;33m:\u001b[0m  \u001b[1;31m# stop if requested\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1702\u001b[0m                     \u001b[1;32mreturn\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32mc:\\ProgramData\\Anaconda3\\lib\\site-packages\\backtrader\\strategy.py\u001b[0m in \u001b[0;36m_oncepost\u001b[1;34m(self, dt)\u001b[0m\n\u001b[0;32m    307\u001b[0m         \u001b[0mminperstatus\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_getminperstatus\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    308\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mminperstatus\u001b[0m \u001b[1;33m<\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 309\u001b[1;33m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    310\u001b[0m         \u001b[1;32melif\u001b[0m \u001b[0mminperstatus\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    311\u001b[0m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnextstart\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m  \u001b[1;31m# only called for the 1st value\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_12716\\2168814799.py\u001b[0m in \u001b[0;36mnext\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m     51\u001b[0m         \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     52\u001b[0m             \u001b[1;31m# 执行卖出条件判断\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 53\u001b[1;33m             \u001b[0mdown\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdata0\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m<\u001b[0m \u001b[0mbuy_price\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     54\u001b[0m             \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcrossover\u001b[0m \u001b[1;33m<\u001b[0m \u001b[1;36m0\u001b[0m \u001b[1;32mor\u001b[0m \u001b[0mdown\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     55\u001b[0m                 \u001b[1;31m# 执行卖出\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mUnboundLocalError\u001b[0m: local variable 'buy_price' referenced before assignment"
     ]
    }
   ],
   "source": [
    "# 创建Cerebro引擎  \n",
    "cerebro = bt.Cerebro()  \n",
    "\n",
    "# 设置回测时间范围\n",
    "start_date = datetime(2000, 4, 3)\n",
    "end_date = datetime(2024, 5, 22)\n",
    "  \n",
    "# 添加数据源  \n",
    "# data0 = MyStockData(dataname=data_stock, fromdate=start_date, todate=end_date) \n",
    "data0 = bt.feeds.PandasData(dataname=data_stock, fromdate=start_date, todate=end_date)\n",
    "cerebro.adddata(data0)  \n",
    "\n",
    "data1 = MySignalData(dataname=data_signals, fromdate=start_date, todate=end_date)\n",
    "#data1 = bt.feeds.PandasData(dataname=data_signals, fromdate=start_date, todate=end_date) \n",
    "cerebro.adddata(data1)  \n",
    "  \n",
    "# 添加策略  \n",
    "cerebro.addstrategy(MyStrategy)  \n",
    "  \n",
    "# 设置初始资金和手续费\n",
    "start_cash = 1000000\n",
    "cerebro.broker.setcash(start_cash)\n",
    "cerebro.broker.setcommission(commission=0.002)\n",
    "\n",
    "# 添加策略分析指标\n",
    "cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='tradeanalyzer')  \n",
    "cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annualReturn')  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='annualizedReturns', tann=252)  # 使用 'annualizedReturns' 代替 '_Returns'  \n",
    "cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')  \n",
    "cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpeRatio')  # 使用 'sharpeRatio' 代替 'sharpe'  \n",
    "cerebro.addanalyzer(bt.analyzers.Returns, _name='totalReturns')  # 使用 'totalReturns' 代替重复的 'returns'  \n",
    "# 假设 'TimeReturn' 是有效的分析器  \n",
    "cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='timeReturn')  # 假设这是有效的\n",
    "\n",
    "#cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')\n",
    "\n",
    "\n",
    "# 运行回测\n",
    "results = cerebro.run()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#获取回测结果并打印\n",
    "port_value = cerebro.broker.getvalue()\n",
    "pnl = port_value - start_cash\n",
    "print(f\"初始资金: {start_cash}\\n回测期间：{start_date.strftime('%Y%m%d')}:{end_date.strftime('%Y%m%d')}\")\n",
    "print(f\"总资金: {round(port_value, 2)}\")\n",
    "print(f\"净收益: {round(pnl, 2)}\")#计算胜率\n",
    "total_trades = results[0].analyzers.tradeanalyzer.get_analysis()['total']['total']\n",
    "won_trades = results[0].analyzers.tradeanalyzer.get_analysis()['won']['total']\n",
    "win_rate = (won_trades / total_trades) * 100 if total_trades > 0 else 0\n",
    "print('总交易次数:', total_trades)\n",
    "print('盈利次数:', won_trades)\n",
    "print('胜率%:', win_rate)\n",
    "# 打印分析器输出结果\n",
    "# print(f\"初始资金: {start_cash}\\n回测期间:{start_date.strftime('%Y-%m-%d')} : {end_date.strftime('%Y-%m-%d')}\")\n",
    "# print('年度汇报:', results[0].analyzers.annualReturn.get_analysis())\n",
    "print('年化收益%:', results[0].analyzers.annualizedReturns.get_analysis()['rnorm100'])\n",
    "print('最大回撤比例%:', results[0].analyzers.drawdown.get_analysis().max.drawdown)\n",
    "print('夏普比率:', results[0].analyzers.sharpeRatio.get_analysis()['sharperatio'])\n",
    "print('累计收益：', results[0].analyzers.totalReturns.get_analysis()['rtot'])\n",
    "\n",
    "print(f'最后投资金额：{round(cerebro.broker.getvalue(), 2)}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 假设 annual_return_data 是您的 OrderedDict 数据  \n",
    "annual_return_data =  results[0].analyzers.annualReturn.get_analysis()\n",
    "  \n",
    "# 设定年份字段的最小宽度（不包括负号）和小数点后的位数  \n",
    "year_field_width = 4  # 假设年份最多4位  \n",
    "decimal_places = 4  # 小数点后的位数  \n",
    "  \n",
    "# 使用列表推导式来格式化字符串，确保小数点对齐  \n",
    "# 对于年份，我们不需要特殊处理，直接格式化即可  \n",
    "# 对于回报值，我们使用字符串格式化来确保它有一个固定的宽度，包括小数点和小数部分  \n",
    "lines = [f\"{year:>{year_field_width}d}, {return_value:+.{decimal_places}f}\" for year, return_value in annual_return_data.items()]  \n",
    "  \n",
    "# 使用 '\\n' 拼接这些行以换行  \n",
    "output = '\\n'.join(lines)  \n",
    "  \n",
    "# 打印结果  \n",
    "print('年度汇报:\\n' + output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cerebro.plot(iplot=True,)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from backtrader_plotting import Bokeh\n",
    "from backtrader_plotting.schemes import Tradimo\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plotconfig = {\n",
    "    'id:ind#0': dict(\n",
    "        subplot=True,\n",
    "    ),\n",
    "}\n",
    "b=Bokeh(style='bar',tabs='multi',scheme=Tradimo())# 传统白底，多页\n",
    "cerebro.plot(b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import quantstats as qs\n",
    "    \n",
    "returns.index = returns.index.tz_convert(None)\n",
    "benchmark_ret = rawdata.close.pct_change().fillna(0)\n",
    "\n",
    "qs.reports.html(returns, benchmark=benchmark_ret, \n",
    "                output='stats.html', title='Stock Sentiment')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
